package hip.util.algorithm;

import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.log4j.Logger;

public class ConsistentHash<T> {
    public static Logger logger = Logger.getLogger(ConsistentHash.class);
    private final HashAlgo hashAlgo;
    private final int numOfReplicas;// 节点的复制因子,实际节点个数 * numOfReplicas =虚拟节点个数
    private final SortedMap<Long, T> circle = new TreeMap<Long, T>();// 存储虚拟节点的hash值到真实节点的映射

    public ConsistentHash(HashAlgo hashAlgo, int numOfReplicas, Collection<T> nodes) {
        this.hashAlgo = hashAlgo;
        this.numOfReplicas = numOfReplicas;
        for (T node : nodes) {
            add(node);
        }
    }

    public void add(T node) {
        for (int i = 0; i < numOfReplicas; i++) {
            // 对于一个实际机器节点 node, 对应 numOfReplicas 个虚拟节点
            /*
             * 不同的虚拟节点(i不同)有不同的hash值,但都对应同一个实际机器node
             * 虚拟node一般是均衡分布在环上的,数据存储在顺时针方向的虚拟node上
             */
            circle.put(hashAlgo.hash(node.toString() + i), node);
        }
    }

    public void remove(T node) {
        for (int i = 0; i < numOfReplicas; i++) {
            circle.remove(hashAlgo.hash(node.toString()) + i);
        }
    }

    /*
     * 获得一个最近的顺时针节点,根据给定的key 取Hash
     * 然后再取得顺时针方向上最近的一个虚拟节点对应的实际节点
     * 再从实际节点中取得 数据
     */
    public T get(Object key) {
        if (circle.isEmpty()) {
            return null;
        }
        long hash = hashAlgo.hash((String) key);// node 用String来表示,获得node在哈希环中的hashCode
        if (!circle.containsKey(hash)) {//数据映射在两台虚拟机器所在环之间,就需要按顺时针方向寻找机器
            SortedMap<Long, T> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }

    public long getSize() {
        return circle.size();
    }

    /*
     *  查看MD5算法生成的hashCode值---表示整个哈希环中各个虚拟节点位置
     */
    public void checkPosition() {
        Set<Long> sets = circle.keySet();
        SortedSet<Long> sortedSets = new TreeSet<Long>(sets);//集合排序
        for (Long hashCode : sortedSets) {
            logger.info(hashCode);
        }

        logger.info("----each location 's distance are follows: ----");
        /*
         *  查看用MD5算法生成的long hashCode 相邻两个hashCode的差值
         */
        Iterator<Long> it = sortedSets.iterator();
        Iterator<Long> it2 = sortedSets.iterator();
        if (it2.hasNext()) it2.next();
        long keyPre, keyAfter;
        while (it.hasNext() && it2.hasNext()) {
            keyPre = it.next();
            keyAfter = it2.next();
            logger.info(keyAfter - keyPre);
        }
    }


}
